/*
 Xern Entity Relocation Network
 Copyright (C) 2011 Ronie Salgado <roniesalg@gmail.com>

 This library is free software; you can redistribute it and/or
 modify it under the terms of the GNU Lesser General Public
 License as published by the Free Software Foundation; either
 version 2.1 of the License, or (at your option) any later version.

 This library is distributed in the hope that it will be useful,
 but WITHOUT ANY WARRANTY; without even the implied warranty of
 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 Lesser General Public License for more details.

 You should have received a copy of the GNU Lesser General Public
 License along with this library; if not, write to the Free Software
 Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301
*/

#include <OgreEntity.h>
#include <boost/uuid/uuid_io.hpp>
#include <boost/lexical_cast.hpp>
#include "Xern/IResourceLoadedCallback.hpp"
#include "Xern/DataFilter.hpp"
#include "Xern/FilterStream.hpp"
#include "Xern/Entity.hpp"
#include "Xern/Engine.hpp"
#include "Xern/Exceptions.hpp"
#include "Xern/Log.hpp"
#include "Xern/ResourceManager.hpp"
#include "GeometryInstance.hpp"
#include "Material.hpp"
#include "OgreRenderer.hpp"
#include "SceneNode.hpp"

namespace XernOgre
{
	// GeometryLoadedCallback.
	class GeometryLoadedCallback: public IResourceLoadedCallback
	{
	public:
		GeometryLoadedCallback(boost::shared_ptr<GeometryInstance> instance);
		~GeometryLoadedCallback();

		virtual void OnLoadedResource(boost::shared_ptr<IResource> resource);

	private:
		boost::shared_ptr<GeometryInstance> instance;
	};

	GeometryLoadedCallback::GeometryLoadedCallback(boost::shared_ptr<GeometryInstance> instance)
		: instance(instance)
	{
	}

	GeometryLoadedCallback::~GeometryLoadedCallback()
	{
	}

	void GeometryLoadedCallback::OnLoadedResource(boost::shared_ptr<IResource> resource)
	{
		boost::shared_ptr<const Geometry> geometry = boost::dynamic_pointer_cast<Geometry> (resource);
		if(!geometry)
			throw XernException("Expected geometry.");

		// Set the instance geometry.
		instance->geometry = geometry;

		// Create the entity name.
		std::string name = instance->renderer->CreateEntityName();

		// Create the entity.
		Ogre::MeshPtr mesh = geometry->GetMesh();
		instance->entity = instance->scene->createEntity(name, mesh->getName(), mesh->getGroup());

		// Set the material.
		if(!geometry->GetMaterialName().empty())
			instance->SetMaterial(geometry->GetMaterialName());

		// Perform the delay attaches.
		GeometryInstance::DelayAttaches::iterator it = instance->delayAttaches.begin();
		for(; it != instance->delayAttaches.end(); it++)
		{
			boost::shared_ptr<SceneNode> node = *it;
			node->AttachObject(instance);
		}

		instance->delayAttaches.clear();

		// Play the delayed animation.
		if(!instance->delayedAnim.empty())
			instance->PlayAnimation(instance->delayedAnim, instance->delayedAnimLoop);
	}

	// GeometryInstance.
	GeometryInstance::GeometryInstance(boost::shared_ptr<const Geometry> geometry,
			boost::shared_ptr<OgreRenderer> renderer,
			Ogre::SceneManager *scene)
		: renderer(renderer), geometry(geometry), scene(scene)
	{
		id = nil_uuid();
		currentState = NULL;
		registeredAnimation = false;

		// Create the entity name.
		std::string name = renderer->CreateEntityName();

		// Create the entity.
		Ogre::MeshPtr mesh = geometry->GetMesh();
		entity = scene->createEntity(name, mesh->getName(), mesh->getGroup());
		entity->setCastShadows(geometry->GetShadowCasting());

		// Set the material.
		if(!geometry->GetMaterialName().empty())
			SetMaterial(geometry->GetMaterialName());
	}

	GeometryInstance::GeometryInstance(boost::shared_ptr<OgreRenderer> renderer)
		: renderer(renderer), scene(renderer->GetSceneManager())
	{
		currentState = NULL;
		registeredAnimation = false;
		entity = NULL;
	}

	GeometryInstance::~GeometryInstance()
	{
		scene->destroyEntity(entity);
	}

	Ogre::MovableObject *GeometryInstance::GetMovable()
	{
		return entity;
	}

	Ogre::Entity *GeometryInstance::GetEntity()
	{
		return entity;
	}

	IResource::ResourceFlags GeometryInstance::GetFlags() const
	{
		return ResourceFlags(RFL_MOVABLE_OBJECT | RFL_SERIALIZABLE);
	}

	const uuid &GeometryInstance::GetId() const
	{
		return this->id;
	}

	void GeometryInstance::SetId(const uuid &id)
	{
		this->id = id;
	}

	const char *GeometryInstance::GetMimeType() const
	{
		return "application/vnd.xern.geom.instance";
	}

	const char *GeometryInstance::GetFileExtension() const
	{
		return "";
	}

	bool GeometryInstance::IsFileSerializable() const
	{
		return false;
	}

	bool GeometryInstance::IsMemorySerializable() const
	{
		return true;
	}

	void GeometryInstance::UpdateState(boost::shared_ptr<IDataFilter> stateData)
	{
		try
		{
			// Try to read the update object.
			FilterStream in(stateData);
			JsonObject update;
			in >> update;

			// Read the update type.
			const JsonString &type = boost::get<JsonString> (update["type"]);

			if(type == "play")
			{
				// Play the animation.
				PlayAnimation(boost::get<JsonString> (update["name"]).string(),
							 boost::get<JsonNumber> (update["loop"]));
			}
		}
		catch(const XernException &e)
		{
			LogConsole->Error("Failed to read geom instance update: %s", e.what());
		}
		catch(const boost::bad_get &e)
		{
			LogConsole->Error("Incomplete/Invalid geometry instance update.");
		}

	}

	boost::shared_ptr<IDataFilter>
	GeometryInstance::GetSerializedData() const
	{
		// Create the json object.
		JsonObject object;
		object["geometry"] = boost::lexical_cast<std::string> (geometry->GetId());

		// Send the current animation.
		if(currentState)
		{
			object["anim"] = currentState->getAnimationName();
			object["anim_loop"] = currentState->getLoop();
		}

		// Serialize the data.
		boost::shared_ptr<StringFilter> filter(new StringFilter());
		filter->GetStream() << object << std::ends;

		// Return the data.
		return filter;
	}

	void GeometryInstance::SetMaterial(const std::string &name)
	{
		entity->setMaterialName(name, Ogre::ResourceGroupManager::DEFAULT_RESOURCE_GROUP_NAME);
	}

	void GeometryInstance::SetMaterial(boost::shared_ptr<IMaterial> material)
	{
		if(!material)
			return;

		boost::shared_ptr<Material> omat = boost::dynamic_pointer_cast<Material> (material);
		entity->setMaterial(omat->material);
	}

	AABox GeometryInstance::GetBoundingBox() const
	{
		const Ogre::AxisAlignedBox &box = entity->getBoundingBox();
		const Ogre::Vector3 &min = box.getMinimum();
		const Ogre::Vector3 &max = box.getMaximum();
		return AABox(Vector3(min.x, min.y, min.z),
					 Vector3(max.x, max.y, max.z));
	}

	void GeometryInstance::SetBoundingBox(const AABox &box)
	{
	}

	boost::shared_ptr<Entity> GeometryInstance::GetParent() const
	{
		return parent.lock();
	}

	void GeometryInstance::SetParent(boost::shared_ptr<Entity> parent)
	{
		this->parent = parent;
	}

	void GeometryInstance::DelayAttach(boost::shared_ptr<SceneNode> node)
	{
		assert(entity == NULL);
		delayAttaches.insert(node);
	}

	void GeometryInstance::AttachToBone(const std::string &bone, boost::shared_ptr<IMovableObject> object)
	{
		boost::shared_ptr<IOgreObject> oobj = boost::dynamic_pointer_cast<IOgreObject> (object);
		if(!oobj)
			throw XernException("Expected ogre object.");

		// Attach the object.
		entity->attachObjectToBone(bone, oobj->GetMovable());

		// Store the object.
		attachedObjects.insert(object);
	}

	void GeometryInstance::DetachFromBone(const std::string &bone, boost::shared_ptr<IMovableObject> object)
	{
		boost::shared_ptr<IOgreObject> oobj = boost::dynamic_pointer_cast<IOgreObject> (object);
		if(!oobj)
			throw XernException("Expected ogre object.");

		// Detach the object.
		entity->detachObjectFromBone(oobj->GetMovable());

		// Remove the object.
		attachedObjects.erase(object);
	}

	void GeometryInstance::PlayAnimation(const std::string &name, bool looped)
	{
		if(!registeredAnimation)
		{
			renderer->RegisterAnimated(shared_from_this());
			registeredAnimation = true;
		}

		Ogre::AnimationState *state = entity->getAnimationState(name);

		// Disable the old state.
		if(currentState != state && currentState)
			currentState->setEnabled(false);

		// Enable the new state.
		if(state)
		{
			state->setLoop(looped);
			state->setTimePosition(0.0f);
			state->setEnabled(true);

			// Send the play anim message.
			JsonObject update;
			update["type"] = "play";
			update["name"] = name;
			update["loop"] = looped;

			boost::shared_ptr<StringFilter> data(new StringFilter());
			data->GetStream() << update;
			Engine::Get().GetResourceManager()->UpdateState(shared_from_this(), data);
		}

		// Store the animation state.
		currentState = state;
	}

	void GeometryInstance::Frame(float delta)
	{
		if(currentState)
		{
			currentState->addTime(delta);

			if(currentState->hasEnded())
			{
				boost::shared_ptr<Entity> ent = parent.lock();
				if(ent)
					ent->OnEvent("end_animation", "renderer", currentState->getAnimationName());
			}
		}
	}

	void GeometryInstance::SetCustomParameter(size_t index, const SVector4 &param)
	{
		if(!entity)
			return;

		int numsub = entity->getNumSubEntities();
		for(int i = 0; i < numsub; i++)
		{
			Ogre::SubEntity *sub = entity->getSubEntity(i);
			sub->setCustomParameter(index, Ogre::Vector4(param.x, param.y, param.z, param.w));
		}
	}

	void GeometryInstance::SetTransform(const ReversibleTransform &trans)
	{
		transform = trans;
	}

	const ReversibleTransform &GeometryInstance::GetTransform() const
	{
		return transform;
	}

	boost::shared_ptr<GeometryInstance>
	GeometryInstance::LoadInstance(boost::shared_ptr<OgreRenderer> renderer, const JsonObject &data)
	{
		// Create the geometry instance.
		boost::shared_ptr<GeometryInstance> instance(new GeometryInstance(renderer));

		// Parse the uuid;
		uuid geomId = boost::lexical_cast<uuid> (boost::get<JsonString> (data["geometry"]).string());

		// Delay animation.
		if(data.has_key("anim"))
		{
			instance->delayedAnim = boost::get<JsonString> (data["anim"]).string();
			instance->delayedAnimLoop = boost::get<JsonNumber> (data["anim_loop"]);
		}

		// Delay load the geometry.
		boost::shared_ptr<GeometryLoadedCallback> callback(new GeometryLoadedCallback(instance));
		boost::shared_ptr<ResourceManager> resourceManager = Engine::Get().GetResourceManager();
		resourceManager->DelayedLoad(geomId, callback);

		// Return the instance.
		return instance;
	}

}; // namespace XernOgre
